#ifndef NANE_SCRIPTOBJECT_H
#   define NANE_SCRIPTOBJECT_H

#   include "nane/core/SubSystemProduct.h"

#   include "nane/core/typetraits.h"
#   include "nane/core/function/ForwardParamT.h"
#   include "nane/ScriptSystem/ScriptSerializer.h"

namespace nane
{

    class ScriptObject
        : public SubSystemProduct
    {
    public:
        class Implementation;
        ScriptObject( const NullType& = NullPtr );
        ScriptObject( Implementation* _impl );
        ScriptObject( const ScriptObject& _other );

    private:
        template<typename T>
        struct ObjectUnwrapper
        {
            typedef T Type;
            static T Unwrap( ScriptObject*, const T& _object )
            {
                return _object;
            }
        };
        
        template<typename RT, typename P1, typename P2, typename P3, typename P4>
        struct InvokeSpecialization
        {
            static RT Invoke( ScriptObject* _this, P1 _p1, P2 _p2, P3 _p3, P4 _p4 )
            {
                ScriptParamsBuffer<P1, P2, P3, P4> args(_p1, _p2, _p3, _p4);
                ScriptParamsBuffer<RT, NullType, NullType, NullType> results;
                if( _this->InvokeImpl_(&args, &results) == false )
                {
                    LOG_ERROR("Failed to invoke script object");
                }
                results.Seek(0);
                int resultsNum = 0;
                IO<IOUnsafe>::Read(&results, &resultsNum);
                if( resultsNum != 1 )
                {
                    LOG_ERROR("Invalid results number (%1)") << resultsNum;
                }
                typedef typename ObjectUnwrapper<RT>::Type WRT;
                WRT result;
                if( ScriptSerializerType<WRT>::Deserialize(&results, &result) == false )
                {
                    LOG_ERROR("Failed to deserialize invoke result");
                }
                return ObjectUnwrapper<RT>::Unwrap(_this, result);
            }
        };
        
        template<typename P1, typename P2, typename P3, typename P4>
        struct InvokeSpecialization<void, P1, P2, P3, P4>
        {
            static void Invoke( ScriptObject* _this, P1 _p1, P2 _p2, P3 _p3, P4 _p4 )
            {
                ScriptParamsBuffer<P1, P2, P3, P4> args(_p1, _p2, _p3, _p4);
                if( _this->InvokeImpl_(&args, NULL) == false )
                {
                    LOG_ERROR("Failed to invoke script object");
                }
            }
        };
        
    public:
        template<typename RT, typename P1, typename P2, typename P3, typename P4>
        RT Invoke( P1 _p1, P2 _p2, P3 _p3, P4 _p4 )
        {
            return InvokeSpecialization<RT, P1, P2, P3, P4>::Invoke(this, _p1, _p2, _p3, _p4);
        }
        
        template<typename RT, typename P1, typename P2, typename P3>
        RT Invoke( P1 _p1, P2 _p2, P3 _p3 )
        {
            return Invoke<RT, P1, P2, P3, NullType>(_p1, _p2, _p3, NullPtr);
        }
        
        template<typename RT, typename P1, typename P2>
        RT Invoke( P1 _p1, P2 _p2 )
        {
            return Invoke<RT, P1, P2, NullType, NullType>(_p1, _p2, NullPtr, NullPtr);
        }
        
        template<typename RT, typename P1>
        RT Invoke( P1 _p1 )
        {
            return Invoke<RT, P1, NullType, NullType, NullType>(_p1, NullPtr, NullPtr, NullPtr);
        }

        template<typename RT>
        RT Invoke()
        {
            return Invoke<RT, NullType, NullType, NullType, NullType>(NullPtr, NullPtr, NullPtr, NullPtr);
        }
               
        const char* ToString();
        
    private:
        bool InvokeImpl_( IInput* _args, IOutput* _results );
        
    public:
        Implementation* m;
    };
    
    template<>
    struct ScriptObject::ObjectUnwrapper<ScriptObject>
    {
        typedef IScriptObject* Type;
        static ScriptObject Unwrap( ScriptObject* _this, IScriptObject* _object );
    };
    
    
    template<>
    struct ScriptWrapT<ScriptObject>
    {
        static const EScriptValueType Type();
        static void SetValue( TScriptValue& _scriptValue, EScriptValueType _type, ScriptObject _value );
    };

}   // namespace nane

#endif  // NANE_SCRIPTOBJECT_H
